package net.qiqbframework.modelling.domain;


import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.commandhandling.CommandMessageExecutableHandler;
import net.qiqbframework.common.utils.AnnotationUtil;
import net.qiqbframework.common.utils.ListUtil;
import net.qiqbframework.common.utils.ReflectionUtil;
import net.qiqbframework.messaging.annotation.AnnotatedHandlerInspector;
import net.qiqbframework.messaging.annotation.MessageExecutableHandler;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static net.qiqbframework.common.utils.ReflectionUtil.NOT_RECURSIVE;

@Slf4j
public class AnnotatedAggregateModel<T> implements AggregateModel<T> {

    private static final String JAKARTA_PERSISTENCE_ID = "jakarta.persistence.Id";
    private static final String JAVAX_PERSISTENCE_ID = "javax.persistence.Id";

    private final Class<? extends T> inspectedType;

    private String naming;

    private String labelNaming;

    private final AnnotatedHandlerInspector<T> handlerInspector;
    private final Map<Class<?>, List<MessageExecutableHandler>> allCommandHandlerInterceptors;
    private final Map<Class<?>, List<MessageExecutableHandler>> allCommandHandlers;


    private final Map<String, Class<?>> types;
    private final Map<Class<?>, String> declaredTypes;
    private Member identifierMember;
    /**
     * 所有业务ID
     */
    private final Map<String, BizIdentifierModel<?>> bizIdentifierModelMap;

    private Member versionMember;

    private final ThreadLocal<Boolean> initializing = new ThreadLocal<>();
    private volatile boolean initialized;


    public AnnotatedAggregateModel(Class<? extends T> aggregateType, AnnotatedHandlerInspector<T> handlerInspector) {
        this.inspectedType = aggregateType;
        this.types = new HashMap<>();
        this.declaredTypes = new HashMap<>();
        this.allCommandHandlerInterceptors = new HashMap<>();
        this.allCommandHandlers = new HashMap<>();
        this.bizIdentifierModelMap = new HashMap<>();

        this.handlerInspector = handlerInspector;
    }

    protected void initialize() {
        initializing.set(Boolean.TRUE);
        inspectNaming();
        inspectFieldsAndMethods();
        inspectAggregateTypes();
        prepareHandlers();
        initialized = true;
        initializing.remove();
    }

    private void inspectNaming() {
        this.naming = AnnotationUtil.findAnnotationAttributes(inspectedType, AggregateRoot.class)
                .map(map -> (String) map.get("naming")).filter(i -> !i.isEmpty())
                .orElse(inspectedType.getName());
        this.labelNaming = AnnotationUtil.findAnnotationAttributes(inspectedType, AggregateRoot.class)
                .map(map -> (String) map.get("labelNaming")).filter(i -> !i.isEmpty())
                .orElse(inspectedType.getSimpleName());

    }

    private void prepareHandlers() {
        for (Map.Entry<Class<?>, SortedSet<MessageExecutableHandler>> handlersPerType
                : handlerInspector.getAllHandlers().entrySet()) {
            Class<?> type = handlersPerType.getKey();
            for (MessageExecutableHandler handler : handlersPerType.getValue()) {
                if (handler.unwrap(CommandMessageExecutableHandler.class).isPresent()) {
                    if (Modifier.isAbstract(type.getModifiers()) && handler.unwrap(Constructor.class).isPresent()) {
                        throw new AggregateModellingException(format(
                                "An abstract aggregate %s cannot have @CommandHandler on constructor.", type
                        ));
                    }
                    addHandler(allCommandHandlers, type, handler);
                }
            }
        }

        for (Map.Entry<Class<?>, SortedSet<MessageExecutableHandler>> interceptorsPerType
                : handlerInspector.getAllInterceptors().entrySet()) {
            Class<?> type = interceptorsPerType.getKey();
            for (MessageExecutableHandler handler : interceptorsPerType.getValue()) {
                addHandler(allCommandHandlerInterceptors, type, handler);
            }
        }
        validateCommandHandlers();
    }


    private void addHandler(Map<Class<?>, List<MessageExecutableHandler>> handlers, Class<?> type,
                            MessageExecutableHandler handler) {
        handlers.computeIfAbsent(type, t -> new ArrayList<>())
                .add(handler);
    }

    @Override
    public void addCommandHandler(MessageExecutableHandler messageHandlingMember) {
        // 只给当前类添加
        this.allCommandHandlers.computeIfAbsent(inspectedType, t -> new ArrayList<>()).add(messageHandlingMember);
        validateCommandHandlers();
    }

    /**
     * 在多态聚合层次结构中，多个聚合中不得有多个创建（工厂）命令处理程序（具有相同的命令名称）
     */
    private void validateCommandHandlers() {
        List<List<MessageExecutableHandler>> handlers = new ArrayList<>(allCommandHandlers.values());
        for (int i = 0; i < handlers.size() - 1; i++) {
            List<CommandMessageExecutableHandler> factoryCommands1 = factoryCommands(handlers.get(i));
            List<CommandMessageExecutableHandler> factoryCommands2 = factoryCommands(handlers.get(i + 1));
            for (CommandMessageExecutableHandler handler1 : factoryCommands1) {
                for (CommandMessageExecutableHandler handler2 : factoryCommands2) {
                    String commandName1 = handler1.commandNaming();
                    String commandName2 = handler2.commandNaming();
                    if (commandName1.equals(commandName2)) {
                        Class<?> declaringClass1 = handler1.declaringClass();
                        Class<?> declaringClass2 = handler2.declaringClass();
                        if (!declaringClass1.equals(declaringClass2)) {
                            throw new AggregateModellingException(format(
                                    "Aggregates %s and %s have the same creation @CommandHandler %s",
                                    declaringClass1,
                                    declaringClass2,
                                    commandName1));
                        }
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private List<CommandMessageExecutableHandler> factoryCommands(
            List<MessageExecutableHandler> handlers) {
        return handlers.stream()
                .map(h -> h.unwrap(CommandMessageExecutableHandler.class))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .filter(CommandMessageExecutableHandler::isFactoryHandler)
                .map(h -> (CommandMessageExecutableHandler) h)
                .collect(toList());
    }

    private void inspectAggregateTypes() {
        for (Class<?> type : handlerInspector.getAllHandlers().keySet()) {
            String declaredType = findDeclaredType(type);
            types.put(declaredType, type);
            declaredTypes.put(type, declaredType);
        }
    }

    private String findDeclaredType(Class<?> type) {
        return AnnotationUtil.findAnnotationAttributes(type, AggregateRoot.class)
                .map(map -> (String) map.get("type")).filter(i -> !i.isEmpty())
                .orElse(type.getSimpleName());
    }

    private void inspectFieldsAndMethods() {

        List<Member> entityIdMembers = new ArrayList<>();
        List<Member> persistenceIdMembers = new ArrayList<>();
        List<Member> aggregateVersionMembers = new ArrayList<>();
        for (Class<?> handledType : handlerInspector.getAllInspectedTypes()) {

            for (Field field : ReflectionUtil.fieldsOf(handledType, NOT_RECURSIVE)) {
                AnnotationUtil.findAnnotationAttributes(field, BizIdentifier.class).ifPresent(attributes -> {
                    final BizIdentifierModel<?> objectBusinessIdModel = new AnnotatedMethodBizIdentifierModel(field, this);
                    bizIdentifierModelMap.put(objectBusinessIdModel.modelName(), objectBusinessIdModel);
                });
                AnnotationUtil.findAnnotationAttributes(field, EntityIdentifier.class).ifPresent(attributes -> entityIdMembers.add(field));
                AnnotationUtil.findAnnotationAttributes(field, JAKARTA_PERSISTENCE_ID).ifPresent(
                        attributes -> persistenceIdMembers.add(field)
                );
                AnnotationUtil.findAnnotationAttributes(field, JAVAX_PERSISTENCE_ID).ifPresent(
                        attributes -> persistenceIdMembers.add(field)
                );
                AnnotationUtil.findAnnotationAttributes(field, AggregateVersion.class).ifPresent(
                        attributes -> aggregateVersionMembers.add(field)
                );
            }

            for (Method method : ReflectionUtil.methodsOf(handledType, NOT_RECURSIVE)) {
                AnnotationUtil.findAnnotationAttributes(method, BizIdentifier.class).ifPresent(attributes -> {
                    assertValidValueProvidingMethod(method, BizIdentifier.class.getSimpleName());
                    final BizIdentifierModel<?> objectBusinessIdModel = new AnnotatedMethodBizIdentifierModel(method, this);
                    bizIdentifierModelMap.put(objectBusinessIdModel.modelName(), objectBusinessIdModel);
                });
                AnnotationUtil.findAnnotationAttributes(method, EntityIdentifier.class).ifPresent(attributes -> {
                    assertValidValueProvidingMethod(method, EntityIdentifier.class.getSimpleName());
                    entityIdMembers.add(method);
                });
                AnnotationUtil.findAnnotationAttributes(method, JAKARTA_PERSISTENCE_ID).ifPresent(attributes -> {
                    assertValidValueProvidingMethod(method, JAKARTA_PERSISTENCE_ID);
                    persistenceIdMembers.add(method);
                });
                AnnotationUtil.findAnnotationAttributes(method, JAVAX_PERSISTENCE_ID).ifPresent(attributes -> {
                    assertValidValueProvidingMethod(method, JAVAX_PERSISTENCE_ID);
                    persistenceIdMembers.add(method);
                });
                AnnotationUtil.findAnnotationAttributes(method, AggregateVersion.class).ifPresent(attributes -> {
                    assertValidValueProvidingMethod(method, AggregateVersion.class.getSimpleName());
                    aggregateVersionMembers.add(method);
                });
            }
        }

        findIdentifierMember(ListUtil.distinct(entityIdMembers), ListUtil.distinct(persistenceIdMembers))
                .ifPresent(this::setIdentifierAndRoutingKey);
        setVersionMember(aggregateVersionMembers);
        assertIdentifierValidity(identifierMember);
    }


    private void assertValidValueProvidingMethod(Method method, String annotationName) {
        if (method.getParameterCount() != 0) {
            throw new AggregateModellingException(format(
                    "Aggregate [%s] has an [%s] annotated method [%s] with parameters, "
                            + "whilst none are allowed on such a method.",
                    inspectedType, annotationName, method
            ));
        }
        if (method.getReturnType() == Void.TYPE) {
            throw new AggregateModellingException(format(
                    "Aggregate [%s] has an [%s] annotated method [%s] with void return type, "
                            + "whilst a return value is required for such a method.",
                    inspectedType, annotationName, method
            ));
        }
    }

    private Optional<Member> findIdentifierMember(List<Member> entityIdMembers,
                                                  List<Member> persistenceIdMembers) {
        if (entityIdMembers.size() > 1) {
            final AggregateModellingException e = new AggregateModellingException("Aggregate [%s] has more than one identifier member, while only a single member is allowed.".formatted(inspectedType));
            log.error("聚合[{}]只能有一个 @EntityIdentifier 修饰的成员 ，现在发现了多个成员：[{}]。", inspectedType, entityIdMembers.stream().map(Member::getName).collect(Collectors.joining(",")), e);
            throw e;
        }
        if (!entityIdMembers.isEmpty()) {
            return Optional.of(entityIdMembers.get(0));
        } else if (!persistenceIdMembers.isEmpty()) {
            return Optional.of(persistenceIdMembers.get(0));
        }
        return Optional.empty();
    }

    private void setIdentifierAndRoutingKey(Member identifier) {
        identifierMember = identifier;
    }


    /**
     * Return the given {@code identifierMember}'s {@link Member#getName()}. If the given {@code identifierMember}
     * is of type {@link Method} and it resembles a regular getter method, the {@code "get"} will be stripped off.
     *
     * @param identifierMember the {@link Member} to retrieve the name for
     * @return the identifier name tied to the given {@code identifierMember}
     */
    private String getMemberIdentifierName(Member identifierMember) {
        String identifierName = identifierMember.getName();
        return identifierMember instanceof Method && isGetterByConvention(identifierName)
                ? stripGetterConvention(identifierName)
                : identifierName;
    }

    private boolean isGetterByConvention(String identifierName) {
        return identifierName.startsWith("get")
                && identifierName.length() >= 4
                && Character.isUpperCase(identifierName.charAt(3));
    }

    private String stripGetterConvention(String identifierName) {
        return identifierName.substring(3, 4).toLowerCase() + identifierName.substring(4);
    }

    private void setVersionMember(List<Member> versionMembers) {
        if (versionMembers.isEmpty()) {
            log.debug("No @AggregateVersion annotated Member found.");
            return;
        }
        if (versionMembers.size() > 1) {
            String versionMembersString = versionMembers.stream()
                    .map(Member::getName)
                    .collect(Collectors.joining(", "));
            throw new AggregateModellingException(format(
                    "Aggregate [%s] has two or more @AggregateVersion annotated members, "
                            + "whilst only a single member is allowed.\n "
                            + "The following version members have been found: %s",
                    inspectedType, versionMembersString
            ));
        }

        log.debug(
                "@AggregateVersion annotated Member [{}] has been found and set as the [{}] Aggregate Version.",
                versionMembers.get(0).getName(), inspectedType
        );
        this.versionMember = versionMembers.get(0);
    }

    private void assertIdentifierValidity(Member identifier) {
        if (identifier != null) {
            final Class<?> idClazz = ReflectionUtil.getMemberValueType(identifier);
            if (!IdentifierValidator.getInstance().isValidIdentifier(idClazz)) {
                throw new AggregateModellingException(format(
                        "Aggregate identifier type [%s] should override Object.toString()",
                        idClazz.getName()
                ));
            }

        } else {
            throw new AggregateModellingException("Aggregation type [%s] requires an aggregation root identifier, but cannot be found.".formatted(inspectedType));
        }

    }

    @Override
    public Map<Class<?>, List<MessageExecutableHandler>> allCommandHandlers() {
        return Collections.unmodifiableMap(allCommandHandlers);
    }

    @Override
    public Stream<MessageExecutableHandler> commandHandlers(Class<? extends T> subtype) {
        return handlers(allCommandHandlers, subtype);
    }

    @Override
    public Stream<Class<?>> types() {
        return handlerInspector.getAllHandlers()
                .keySet()
                .stream();
    }


    @Override
    public Optional<EntityModel<?>> parentEntityModel() {
        return Optional.empty();
    }

    @Override
    public Class<? extends T> entityClass() {
        return inspectedType;
    }

    @Override
    public BizIdentifierModel<?> getEntityIdModel() {
        return bizIdentifierModelMap.get(identifierMember.getName());
    }

    @Override
    public BizIdentifierModel<?> getBizIdentifierModel(String businessIdName) {
        return bizIdentifierModelMap.get(businessIdName);
    }

    @Override
    public Collection<BizIdentifierModel<?>> allBizIdentifierModels() {
        return bizIdentifierModelMap.values();
    }


    @Override
    public String type() {
        return declaredTypes.get(inspectedType);
    }

    @Override
    public Optional<Class<?>> type(String declaredType) {
        return Optional.ofNullable(types.getOrDefault(declaredType, null));
    }

    @Override
    public Optional<String> declaredType(Class<?> type) {
        return Optional.ofNullable(declaredTypes.getOrDefault(type, null));
    }

    @Override
    public Long getVersion(T target) {
        return versionMember != null ? ReflectionUtil.<Long>getMemberValue(versionMember, target) : null;
    }

    @Override
    public Map<Class<?>, List<MessageExecutableHandler>> allCommandHandlerInterceptors() {
        return Collections.unmodifiableMap(allCommandHandlerInterceptors);
    }

    @Override
    public Stream<MessageExecutableHandler> commandHandlerInterceptors(Class<? extends T> subtype) {
        return handlers(allCommandHandlerInterceptors, subtype);
    }


    // Backwards compatibility - if you don't specify a designated child,
    //  you should at least get handlers of its first registered parent (if any).
    private Stream<MessageExecutableHandler> handlers(
            Map<Class<?>, List<MessageExecutableHandler>> handlers, Class<?> subtype
    ) {
        Class<?> type = subtype;
        while (!handlers.containsKey(type) && !Objects.equals(type, Object.class) && type.getSuperclass() != null) {
            type = type.getSuperclass();
        }
        return handlers.getOrDefault(type, Collections.emptyList()).stream();
    }

    @Override
    public Object getIdentifier(T target) {
        return identifierMember != null ? ReflectionUtil.getMemberValue(identifierMember, target) : null;
    }

    @Override
    public Map<BizIdentifierModel<?>, Object> getBizIdentifiers(T target) {
        Map<BizIdentifierModel<?>, Object> bizIdentifiers = new HashMap<>();
        for (Map.Entry<String, BizIdentifierModel<?>> entry : bizIdentifierModelMap.entrySet()) {
            bizIdentifiers.put(entry.getValue(), entry.getValue().bizIdentifier(target));
        }
        return bizIdentifiers;
    }


    /**
     * Returns this instance when it is safe to read from. This is either if the current thread is already
     * initializing this instance, or when the instance is fully initialized.
     *
     * @return this instance, as soon as it is safe for reading
     */
    protected AnnotatedAggregateModel<T> whenReadSafe() {
        if (Boolean.TRUE.equals(initializing.get())) {
            // This thread is initializing. To prevent deadlocks, we should return this instance immediately
            return this;
        }
        while (!initialized) {
            // another thread is initializing, it shouldn't take long...
            Thread.yield();
        }
        // we're safe to go
        return this;
    }

    @Override
    public String modelName() {
        return naming;
    }

    @Override
    public String modelLabel() {
        return labelNaming;
    }
}
